/**
* Copyright (c) By zengqh.
*
* This program is just for fun or demo, in the hope that it  
* will be useful, you can redistribute it and/or modify freely.
*
* Time: 2013/03/04
* File: OGLTexture2D.cpp
**/

#include "OGLTexture2D.h"
#include "image/image.h"
#include "OGL.h"

namespace HY
{
OGLTexture2D::OGLTexture2D()
: is_mipmap_(false)
{
	
}

OGLTexture2D::~OGLTexture2D()
{
	release();
}

uint32 OGLTexture2D::getHandle() const
{
	return handle_;
}

bool OGLTexture2D::load(Image* image)
{
	HY_ASSERT(image);

	uint32 width = image->getWidth();
	uint32 height = image->getHeight();
	uint8* data = image->getData();
	uint32 components = image->getComponents();
	uint32 pixel_format = getPixelFormatByImage(image);
	bool is_compressed = image->isCompressed();

	create(width, height, pixel_format, is_compressed, 0);

	uint32 internal_format = getInternalFormat(pixel_format);
	uint32 external_format = getExternalFormat(pixel_format);
	uint32 data_type = getDataType(pixel_format);

	if (image->isCompressed())
	{
		glCompressedTexImage2D(GL_TEXTURE_2D, 0, 
			internal_format, width, height, 0, getDataSize(width, height), data);
	}
	else
	{
		glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 
			0, external_format, data_type, data);
	}

	return true;
}

void OGLTexture2D::bind()
{
	glBindTexture(GL_TEXTURE_2D, handle_);	
}

void OGLTexture2D::unbind()
{
	glBindTexture(GL_TEXTURE_2D, 0);
}

bool OGLTexture2D::create(uint32 width, uint32 height, 
							 uint32 format, bool mipmap, bool compressed)
{
	if (width == 0 || height == 0)
	{
		HY_ASSERT(0);
		return false;
	}

	release();

	is_mipmap_ = mipmap;
	format_ = format;

	glGenTextures(1, &handle_);

	HY_ASSERT(handle_ = 0);

	glBindTexture(GL_TEXTURE_2D, handle_);

	if (!compressed)
	{
		uint32 internal_format = getInternalFormat(format);
		uint32 external_format = getExternalFormat(format);
		uint32 data_type = getDataType(format);

		glTexImage2D(GL_TEXTURE_2D, 0, 
			internal_format, 
			width, height, 0, 
			external_format, 
			data_type, 0);

		checkGLError();
	}

	if (is_mipmap_)
	{
		/** ogl 3.0 above. */
		glGenerateMipmap(GL_TEXTURE_2D);
	}

	updateTexParams();

	return true;
}

void OGLTexture2D::release()
{
	if (handle_)
	{
		glDeleteTextures(1, &handle_);
		handle_ = 0;
	}
}

uint32 OGLTexture2D::getPixelFormatByImage(Image* image)
{
	HY_ASSERT(image);

	int comp = image->getComponents();
	bool is_compressed = image->isCompressed();
	uint32 compress_format = image->getCompressFormat();

	if (!is_compressed)
	{
		switch (comp)
		{
		case 1:
			return PixelFormat::U1;
		case 3:
			return PixelFormat::U3;
		case 4:
			return PixelFormat::U4;
		default:
			HY_ASSERT(0);
		}
	}
	else
	{
		CompressedFormat format = (CompressedFormat)compress_format;
		switch (format)
		{
		case CF_DXT1:
			return PixelFormat::DXT1;
		case CF_DXT3:
			return PixelFormat::DXT3;
		case CF_DXT5:
			return PixelFormat::DXT5;
		default:
			HY_ASSERT(0);
		}
	}

	HY_ASSERT(0);
	return PixelFormat::U3;
}

uint32 OGLTexture2D::getInternalFormat(uint32 format)
{
	return PixelFormatMappping[format];
}

uint32 OGLTexture2D::getExternalFormat(uint32 format)
{
	switch (format)
	{
	case PixelFormat::U1:
	case PixelFormat::U16_1:
	case PixelFormat::F16_1:
	case PixelFormat::F32_1:
		return GL_RED;
	case PixelFormat::U16_2:
	case PixelFormat::F16_2:
	case PixelFormat::F32_2:
		return GL_RG;
	case PixelFormat::D16:
	case PixelFormat::D24:
	case PixelFormat::D32:
		return GL_DEPTH_COMPONENT;
	case PixelFormat::U3:
	case PixelFormat::DXT1:
		return GL_RGB;
	case PixelFormat::DXT3:
	case PixelFormat::DXT5:
	case PixelFormat::U16_4:
	case PixelFormat::F16_4:
	case PixelFormat::F32_4:
		return GL_RGBA;
	default:
		HY_ASSERT(0);
	}

	return GL_RGB;
}

uint32 OGLTexture2D::getDataType(uint32 format)
{
	switch (format)
	{
	case PixelFormat::D16:
		return GL_UNSIGNED_SHORT;

	case PixelFormat::D24:
	case PixelFormat::D32:
		return GL_UNSIGNED_INT;

	case PixelFormat::DXT1:
	case PixelFormat::DXT3:
	case PixelFormat::DXT5:
	case PixelFormat::U1:
	case PixelFormat::U3:
	case PixelFormat::U4:
		return GL_UNSIGNED_BYTE;

	case PixelFormat::U16_1:
	case PixelFormat::U16_4:
	case PixelFormat::U16_2:
		return GL_UNSIGNED_SHORT;

	case PixelFormat::F16_1:
	case PixelFormat::F16_4:
	case PixelFormat::F16_2:
		return GL_HALF_FLOAT;

	case PixelFormat::F32_1:
	case PixelFormat::F32_4:
		return GL_FLOAT;

	default:
		return GL_UNSIGNED_BYTE;
	}

	return GL_UNSIGNED_BYTE;
}

uint32 OGLTexture2D::getRowDataSize(uint32 width)
{
	switch (getInternalFormat(format_))
	{
	case GL_R8:
		return width;

	case GL_DEPTH_COMPONENT16:
		return width * 2;

	case GL_RGB8:
	case GL_DEPTH_COMPONENT24:
		return width * 3;

	case GL_RGBA8:
	case GL_RG16:
	case GL_DEPTH_COMPONENT32:
		return width * 4;

	case GL_RGBA16:
		return width * 8;

	case GL_RGBA16F:
	case GL_RGBA32F:
		return width * 16;

	case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
		return ((width + 3) >> 2) * 8;

	case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
	case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
		return ((width + 3) >> 2) * 16;

	default:
		return 0;
	}
}

uint32 OGLTexture2D::getDataSize(uint32 width, uint32 height)
{
	if (isCompressedFormat())
	{
		return getRowDataSize(width) * ((height + 3) >> 2);
	}
	else
	{
		return getRowDataSize(width) * height;
	}
}

void OGLTexture2D::updateTexParams()
{
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, SamplerAddrMapping[sampler_mode_[Sampler::Address]]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, SamplerAddrMapping[sampler_mode_[Sampler::Address]]);
	
	uint32 filter_mode = sampler_mode_[Sampler::Filter];
	switch (filter_mode)
	{
	case SamplerFilterMode::FILTER_NEAREST:
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		break;
	case SamplerFilterMode::FILTER_BILINEAR:
		if (is_mipmap_) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		else glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
		
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		break;
	case SamplerFilterMode::FILTER_TRILINEAR:
		if (is_mipmap_)
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		else
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		break;
	default:
		HY_ASSERT(0);
		break;
	}
}

}